An XNA clock
program doesn’t need a timer because a timer is effectively built into
the normal game loop. However, the clock I want to code here won’t
display milliseconds so the display only needs to be updated every
second. For that reason it uses the SuppressDraw method to inhibit superfluous Draw calls.
Here are the XnaSimpleClock fields:
Example 3. XNA Project: XnaSimpleClock File: Game1.cs (excerpt showing fields)
public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; SpriteFont segoe14; Viewport viewport; Vector2 textPosition; StringBuilder text = new StringBuilder(); DateTime lastDateTime; . . . }
|
Notice that instead of defining a field of type string named text, I’ve defined a StringBuilder instead. If you’re creating new strings in your Update method for display during Draw (as this program will do), you should use StringBuilder to avoid the heap allocations associated with the normal string type. This program will only be creating a new string every second, so I really didn’t need to use StringBuilder here, but it doesn’t hurt to get accustomed to it. StringBuilder requires a using directive for the System.Text namespace.
Notice also the lastDateTime field. This is used in the Update method to determine if the displayed time needs to be updated.
The LoadContent method gets the font and the viewport of the display:
Example 4. XNA Project: XnaSimpleClock File: Game1.cs (excerpt)
protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); segoe14 = this.Content.Load<SpriteFont>("Segoe14"); viewport = this.GraphicsDevice.Viewport; }
|
The logic to compare two DateTime values to see if the time has changed is just a little tricky because DateTime objects obtained during two consecutive Update calls will always be different because they have will have different Millisecond fields. For this reason, a new DateTime is calculated based on the current time obtained from DateTime.Now, but subtracting the milliseconds:
Example 5. XNA Project: XnaSimpleClock File: Game1.cs (excerpt)
protected override void Update(GameTime gameTime) { // Allows the game to exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit();
// Get DateTime with no milliseconds DateTime dateTime = DateTime.Now; dateTime = dateTime - new TimeSpan(0, 0, 0, 0, dateTime.Millisecond);
if (dateTime != lastDateTime) { text.Remove(0, text.Length); text.Append(dateTime); Vector2 textSize = segoe14.MeasureString(text); textPosition = new Vector2((viewport.Width - textSize.X) / 2, (viewport.Height - textSize.Y) / 2); lastDateTime = dateTime; } else { SuppressDraw(); }
base.Update(gameTime); }
|
At that point it’s easy. If the time has changed, new values of text, textSize, and textPosition are calculated. Because text is a StringBuilder rather than a string, the old contents are removed and the new contents are appended. The MeasureString method of SpriteFont has an overload for StringBuilder, so that call looks exactly the same.
If the time has not changed, SuppressDraw is called. The result: Draw is called only once per second.
DrawString also has an overload for StringBuilder:
Example 6. XNA Project: XnaSimpleClock File: Game1.cs (excerpt)
protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.Navy);
spriteBatch.Begin(); spriteBatch.DrawString(segoe14, text, textPosition, Color.White); spriteBatch.End();
base.Draw(gameTime); }
|
And here’s the result:
SuppressDraw
can be a little difficult to use—I’ve found it particularly tricky
during the time that the program is first starting up—but it’s one of
the primary techniques used in XNA to reduce the power requirements of
the program.